package com.ds.infra.im.client;

import com.ds.infra.im.constant.Command;
import com.ds.infra.im.codec.CustomProtocolDecoder;
import com.ds.infra.im.codec.CustomProtocolEncoder;
import com.ds.infra.im.model.*;
import com.ds.infra.im.protocol.ProtocolMsg;
import com.ds.infra.im.util.JsonUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * 作用：Netty客户端，用于连接服务器并发送消息。
 *
 * @author WeiShaoying
 */
public class NettyClient {

    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 8888;

    private final String userId;
    private Channel channel;

    public NettyClient(String userId) {
        this.userId = userId;
    }

    public void connect() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            // 创建客户端启动引导类Bootstrap，配置客户端NIO线程组和处理器
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // 添加空闲状态检测处理器
                            pipeline.addLast(new IdleStateHandler(0, 20, 0, TimeUnit.SECONDS));

                            // 添加自定义协议编解码器
                            pipeline.addLast(new CustomProtocolDecoder());
                            pipeline.addLast(new CustomProtocolEncoder());

                            // 添加业务处理器
                            pipeline.addLast(new ClientHandler());
                        }
                    });

            // 连接服务端
            ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
            channel = future.channel();

            // 登录
            login();

            // 启动心跳定时任务
            startHeartbeat();

            // 在连接成功后启动控制台输入线程
            handleInput();
            // 等待连接关闭
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
            logger.info("Client shutdown");
        }
    }

    private void handleInput() {
        new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            String currentTarget = null; // 当前聊天对象

            while (true) {
                if (currentTarget == null) {
                    System.out.print("提示：请输入目标用户和消息 (格式: userID:消息内容): ");
                } else {
                    System.out.print("提示：发送给 " + currentTarget + " (或输入 userID:消息 切换目标): ");
                }

                String input = scanner.nextLine().trim();
                // 退出命令
                if (input.equalsIgnoreCase("exit")) {
                    logout();
                    break;
                }
                try {
                    // 检查是否包含目标切换
                    if (input.contains(":")) {
                        String[] parts = input.split(":", 2);
                        currentTarget = parts[0].trim();
                        input = parts[1].trim();
                    }

                    // 必须有有效的目标用户
                    if (currentTarget == null || currentTarget.isEmpty()) {
                        System.out.println("错误：尚未指定目标用户！");
                        continue;
                    }

                    // 发送消息
                    sendMessage(currentTarget, input);

                } catch (Exception e) {
                    System.out.println("发送失败: " + e.getMessage());
                }
            }
            scanner.close();
        }).start();
    }

    private void login() {
        LoginRequest loginRequest = new LoginRequest(userId, "token188");
        sendRequest(Command.LOGIN, loginRequest);
    }

    public void logout() {
        sendRequest(Command.LOGOUT, new LogoutRequest());
        channel.close();
    }

    public void sendMessage(String toUserId, String content) {
        MessageRequest messageRequest = new MessageRequest(toUserId, content);
        sendRequest(Command.MESSAGE, messageRequest);
    }

    private void sendRequest(byte command, Object data) {
        if (channel == null || !channel.isActive()) {
            logger.warn("Channel is not active");
            return;
        }
        try {
            byte[] bytes = JsonUtil.toJson(data).getBytes(StandardCharsets.UTF_8);
            ProtocolMsg protocol = new ProtocolMsg((byte) 1, (byte) 0, command, bytes.length, bytes);
            channel.writeAndFlush(protocol);
        } catch (Exception e) {
            logger.error("Send request error", e);
        }
    }

    private void startHeartbeat() {
        channel.eventLoop().scheduleAtFixedRate(() -> {
            if (channel.isActive()) {
                sendRequest(Command.HEARTBEAT, new HeartbeatRequest());
            }
        }, 0, 10, TimeUnit.SECONDS);
    }

    /**
     * 客户端业务处理器
     */
    private class ClientHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            logger.info("Connected to server");
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            logger.info("Disconnected from server");
            // 尝试重连
            ctx.channel().eventLoop().schedule(() -> {
                try {
                    connect();
                } catch (InterruptedException e) {
                    logger.error("Reconnect error", e);
                }
            }, 5, TimeUnit.SECONDS);
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (!(msg instanceof ProtocolMsg)) {
                logger.warn("Received unknown message type: {}", msg.getClass().getName());
                return;
            }

            ProtocolMsg protocol = (ProtocolMsg) msg;
            byte command = protocol.getCommand();

            switch (command) {
                case Command.LOGIN_RESPONSE:
                    handleLoginResponse(protocol);
                    break;
                case Command.LOGOUT_RESPONSE:
                    handleLogoutResponse(protocol);
                    break;
                case Command.MESSAGE_RESPONSE:
                    handleMessageResponse(protocol);
                    break;
                case Command.MESSAGE:
                    handleMessage(protocol);
                    break;
                case Command.HEARTBEAT_RESPONSE:
                    handleHeartbeatResponse(protocol);
                    break;
                case Command.ERROR_RESPONSE:
                    handleErrorResponse(protocol);
                    break;
                default:
                    logger.warn("Unknown command: {}", command);
                    break;
            }
        }

        private void handleLoginResponse(ProtocolMsg protocol) throws IOException {
            LoginResponse response = JsonUtil.fromJson(protocol.getData(), LoginResponse.class);
            if (response.isSuccess()) {
                logger.info("Login success: {}", response.getMessage());
            } else {
                logger.error("Login failed: {}", response.getMessage());
                channel.close();
            }
        }

        private void handleLogoutResponse(ProtocolMsg protocol) throws IOException {
            LogoutResponse response = JsonUtil.fromJson(protocol.getData(), LogoutResponse.class);
            logger.info("Logout success: {}", response.isSuccess());
        }

        private void handleMessageResponse(ProtocolMsg protocol) throws IOException {
            MessageResponse response = JsonUtil.fromJson(protocol.getData(), MessageResponse.class);
            if (response.isSuccess()) {
                logger.debug("Message sent success: {}", response.getMessage());
            } else {
                logger.error("Message sent failed: {}", response.getMessage());
            }
        }

        private void handleMessage(ProtocolMsg protocol) throws IOException {
            MessageForward forward = JsonUtil.fromJson(protocol.getData(), MessageForward.class);
            logger.info("收到{}消息: {}", forward.getFromUserId(), forward.getContent());
        }

        private void handleHeartbeatResponse(ProtocolMsg protocol) {
            logger.debug("Received heartbeat response");
        }

        private void handleErrorResponse(ProtocolMsg protocol) throws IOException {
            ErrorResponse response = JsonUtil.fromJson(protocol.getData(), ErrorResponse.class);
            logger.error("Error response: {}", response.getMessage());
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;
                if (event.state() == IdleState.WRITER_IDLE) {
                    logger.debug("Sending heartbeat");
                    sendRequest(Command.HEARTBEAT, new HeartbeatRequest());
                }
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            logger.error("Channel error", cause);
            ctx.close();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        if (args.length < 1) {
            System.out.println("Usage: NettyClient <userId>");
            return;
        }

        NettyClient client = new NettyClient(args[0]);
        client.connect();
    }
}
